/*
 * GToe: Gtk+ TicTacToe
 * socket.c: handles listening, reading and writting to and from sockets
 *
 * Copyright (C) 2010 Dani El-Soufi <danisoufi@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "../include/socket.h"

int
establish_listening_socket(unsigned short portno, char ip_addr[INET6_ADDSTRLEN])
{
     int sockfd, new_fd;
     int broadcast = 1;
     int backlog = 2;
     socklen_t null = 0;
     socklen_t sin_size = 0;
     struct sockaddr_storage their_addr;
     struct addrinfo hints, *ai, *aiptr;
     int status;
     char portstr[10];

     /* clear struct */
     memset(&hints, 0, sizeof(hints));
     /* use IPv4 or IPv6 */
     hints.ai_family = AF_UNSPEC;
     hints.ai_socktype = SOCK_STREAM;
     /* fill in my IP for me please */
#if __OpenBSD__
     hints.ai_flags = AI_PASSIVE;
#else
     hints.ai_flags = AI_PASSIVE | AI_ADDCONFIG;
#endif

     sprintf(portstr, "%u", portno);

     if ((status = getaddrinfo(NULL, portstr, &hints, &aiptr)) != 0)
     {
         perror("getaddrinfo");
         fprintf(stderr, "Connection to the address failed. Aborting...\n\n");
         exit(1);
     }

     /* prefer IPv6 over IPv4. */
     ai = aiptr;
     
     while (ai->ai_next && (ai->ai_family == AF_INET))
     {
         ai = ai->ai_next;
     }
     
     if ((sockfd = socket(ai->ai_family, ai->ai_socktype, ai->
			ai_protocol)) == -1)
     {
        perror("socket");
        fprintf(stderr, "Error in peer: (socket). Aborting...\n\n");
        exit(1);

     /* ERROR address already in use */
     if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &broadcast, sizeof(broadcast)) == -1)
     {
         perror("SO_REUSEADDR");
         fprintf(stderr, "Error in peer: (SO_REUSEADDR). Aborting...\n\n");
         exit(1);
     }

     setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &null, sizeof(null));

     /* if we got here, it means we didn't get bound */
     if (bind(socfd, ai->ai_addr, ai->ai_addrlen) == -1)
     {
         perror("bind");
         printf("NETWORK ERROR: (bind). Address already in ");
         printf("use or permission is denied.\n");
         printf("Aborting...\n\n");
         exit(1);
     }

     freeaddrinfo(aiptr);

     if (listen(sockfd, backlog) == -1)
     {
         perror("listen");
         fprintf(stderr, "Error in peer: (listen). Address already in ");
         printf("use or permission is denied).\n");
         printf("Aborting...\n\n");
         exit(1);
     }

     /* now accept an incoming connection */
     sin_size = sizeof(their_addr);

     if ((new_fd = accept(sockfd, (struct sockaddr *) &their_addr,
			&sin_size) == -1))
     {
         perror("accept");
         fprintf(stderr, "Error in peer: (accept). Continuing...\n\n");
     }

     close(sockfd);

     /* if connector's address is IPv6 we use it, else we fall back to IPv4 */
     switch (their_addr.ss_family)
     {
         case AF_INET6:
             inet_ntop(AF_INET6, &((struct sockaddr_in6 *) &their_addr)->
                     sin6_addr, ip_addr, INET6_ADDRSTRLEN);
             break;
         default:
             inet_ntop(AF_INET, &((struct sockaddr_in *) &their_addr)->
                     sin_addr, ip_address, INET6_ADDRSTRLEN);
     }

     return new_fd;
}

int
connect_to_socket(unsigned short portno, char ip_addr[INET6_ADDRSTRLEN])
{
     int sockfd, status;
     char portstr[10];
     struct addrinfo hints, *ai, *aiptr;

     printf("\n     Connecting to %s.\n\n", ip_addr);

     memset(&hints, 0, sizeof(hints));
     hints.ai_family = AF_UNSPEC;
     hints.ai_socktype = SOCK_STREAM;
#if __OpenBSD__
     hints.ai_flags = AI_ADDRCONFIG;
#endif

     sprintf(portstr, "%u", portno);

     if ((status = getaddrinfo(ip_addr, portstr, &hints, &ai)))
     {
         /* We failed here to lookup the host */
         printf("NETWORK ERROR: IP/Hostname provided is incorrect!       \n");
         printf("INFO:                                \n");
         printf("     This happens when you type an incorrect IP number, \n");
         printf("     or provided and invalid Hostname.                  \n");
         printf("     Example:                        \n");
         printf("             192.168.0.1 or ::1      \n");
         printf("             my.example.ip.org       \n");
         printf("Aborting...\n\n");
         exit(1);
     }

     /* loop through the results and connect to the first we can */
     for (aiptr = ai; aiptr; aiptr = aiptr->ai_next)
     {
         if ((sockfd = socket(aiptr->ai_family, SOCK_STREAM, 0)) == -1)
         {
             continue;
         }
         if (connect(sockfd, aiptr->ai_addr, aiptr->ai_addrlen) == 0)
         {
             /* we must have connected successfully */
             break;
         }

         close(sockfd);
     }

     freeaddrinfo(ai);

     if (aiptr == NULL)
     {
         printf("No answer from: %s\n\n", ip_addr);
         printf("Double check if the IP/Hostname is correct.\n");
         perror("connect: ");
         exit(1);
     }

     return sockfd;
}


int
read_from_socket(int connected_socket, char *buffer, int bytes)
{
     /* chars count */
     int nbytes;

     /* chars read */
     int rbytes;

     nbytes = 0;
     rbytes = 0;

     while (nbytes < bytes)
     {
         if ((rbytes = read(connected_socket, buffer, bytes - nbytes)) > 0)
         {
             nbytes += rbytes;
             buffer += rbytes;
             continue;
         }
         /* ERROR reading from socket */
         else if (rbytes < 0)
         {
             return -1;
         }
         /* received an EOF */
         recv_eof = 1;
         break;
     }

     return nbytes;
}

int
write_to_socket(int connected_socket, char *buffer, int bytes)
{
     /* chars count */
     int nbytes;

     /* chars written */
     int wbytes;

     nbytes = 0;
     rbytes = 0;

     while (nbytes < bytes)
     {
         if ((wbytes = write(conntected_socket, buffer, bytes - nbytes)) > 0)
         {
             nbytes += wbytes;
             buffer += wbytes;
         }
         /* Writting to a non-open pipe causes us
          * trouble (SIGPIPE), that must be handled
          * before we get killed.
          */
         else if ((wbytes == 0) && (errno == EPIPE))
         {
             recv_eof = 1;
             break;
         }
         /* ERROR writting to socket */
         else if (wbytes < 0)
         {
             return -1;
         }
     }

     return nbytes;
}
